Skip to content
This repository was archived by the owner on May 5, 2026. It is now read-only.

CodeRabbit: feat(premium): cryptographic session verification for premium bypass#14

Open
TuxCoding wants to merge 3 commits into
masterfrom
feat/premium
Open

CodeRabbit: feat(premium): cryptographic session verification for premium bypass#14
TuxCoding wants to merge 3 commits into
masterfrom
feat/premium

Conversation

@TuxCoding
Copy link
Copy Markdown
Owner

@TuxCoding TuxCoding commented May 2, 2026

Summary by CodeRabbit

  • New Features

    • Premium mode: verified Mojang accounts can bypass password auth and optionally skip pre-join dialogs
    • Player commands: /premium and /freemium to opt-in/out
    • Admin commands: /authme premium and /authme freemium to manage accounts
    • Proxy support: premium state synchronized across proxies and optional proxy-side redirect after auth
  • Configuration

    • New settings to enable premium mode and configure proxy redirect/login target
  • Localization

    • Added premium-related messages for many languages
  • Documentation

    • README, docs/premium.md, and command/config docs updated with usage and behavior details

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

This PR adds a "Premium bypass" feature: Mojang-account verification lets enrolled players skip password authentication. It introduces premium enrollment commands (/premium, /freemium, /authme premium|freemium), persistence of a premium UUID column, proxy synchronization (Bungee/Velocity) and proxy-side login redirect config, PacketEvents-based cryptographic verification (RSA/AES) for direct offline-mode servers, pending-premium caching, and localized messages and docs. Several PacketEvents, platform adapter, proxy bridge, and join/login flows were extended to support verification, pending state, and proxy-aware auto-login.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Server as "Bukkit Server"
    participant PacketEvents as "PacketEvents Listener"
    participant Verifier as "PremiumLoginVerifier"
    participant Mojang as "Mojang API"
    participant Database as "DataSource"
    participant Auth as "Authentication Flow"

    Client->>Server: LOGIN_START
    PacketEvents->>Database: Query premium flag / pending
    Database-->>PacketEvents: premium? / pending?
    alt premium or pending
        PacketEvents->>Verifier: startVerification(connectionKey, username, playerUUID)
        Verifier-->>PacketEvents: verifyToken
        PacketEvents->>Client: ENCRYPTION_REQUEST (server public key + token)
        Client->>Server: ENCRYPTION_RESPONSE (encrypted sharedSecret + token)
        PacketEvents->>Verifier: decrypt token, validate
        PacketEvents->>PacketEvents: install AES/CFB8 ciphers
        Verifier->>Mojang: hasJoined(username, serverHash)
        Mojang-->>Verifier: mojangUuid (optional)
        alt Mojang verification succeeds
            Verifier->>Database: (optional) record/store verified UUID or return verified UUID
            PacketEvents->>Auth: resume login as verified (auto-login)
        else
            PacketEvents->>Auth: resume normal login (no auto-login)
        end
    else
        PacketEvents->>Auth: resume normal login
    end
Loading
sequenceDiagram
    actor Player
    participant Command as "Command Handler"
    participant Service as "PremiumService"
    participant DataSource as "DataSource"
    participant Mojang as "MojangApiService"
    participant Cache as "PlayerCache"
    participant Proxy as "Bungee/Velocity Sender"

    Player->>Command: /premium
    Command->>Service: enablePremium(player)
    Service->>Cache: get PlayerAuth
    Cache-->>Service: PlayerAuth
    alt player UUID is v4 (Mojang)
        Service->>Service: storePremiumUuid (sync)
    else
        Service->>Mojang: fetchUuidByName(playerName)
        Mojang-->>Service: mojangUuid (optional)
        alt mojangUuid found
            Service->>Service: add pending, notify proxy (premium.pending.set)
            Service->>Player: send pending_kick message (sync kick scheduled)
        else
            Service-->>Player: send account_not_found
        end
    end
    Service->>DataSource: updatePremiumUuid(auth)
    DataSource-->>Service: success/failure
    Service->>Proxy: sendPremiumSet/sendPremiumUnset as needed
Loading
sequenceDiagram
    actor Client
    participant Proxy as "Proxy (Bungee/Velocity)"
    participant Backend as "AuthMe Backend"
    participant BackendAuth as "AuthMe Auth Flow"

    Client->>Proxy: Connect (premium username)
    Proxy->>Proxy: check premiumUsernames / pending
    alt username in proxy premium cache
        Proxy->>Client: force online-mode (PreLogin)
    end
    Client->>Proxy: Login (Mojang verified)
    Proxy->>Proxy: mark proxyVerifiedPremium
    Proxy->>Backend: ServerConnectEvent to auth server
    Backend->>Backend: allow connection if proxyVerifiedPremium && not pending
    alt bypass allowed
        BackendAuth->>BackendAuth: auto-login (skip dialog)
    else
        BackendAuth->>BackendAuth: normal auth flow
    end
    Backend->>Proxy: send premium.set/premium.list updates as needed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/premium

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
authme-core/src/main/resources/messages/messages_eu.yml (1)

218-236: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Please proofread the new premium translations.

Line 220 has a Basque typo (gaitugaituta), and the new premium/admin strings could use a quick native-speaker pass before release.

Suggested fix
-    feature_disabled: '&cPremium modua ez dago gaitu zerbitzari honetan.'
+    feature_disabled: '&cPremium modua ez dago gaituta zerbitzari honetan.'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@authme-core/src/main/resources/messages/messages_eu.yml` around lines 218 -
236, Fix the Basque typo in the premium messages by changing the value of the
premium.feature_disabled entry from using "gaitu" to "gaituta", and perform a
native-speaker proofread of all keys under premium (feature_disabled,
account_not_found, already_enabled, enable_success, not_enabled,
disable_success, error) and premium.admin (not_registered, already_enabled,
account_not_found, enable_success, not_enabled, disable_success,
impostor_kicked, kick_reason) to ensure idiomatic phrasing and correct grammar
before merging.
🧹 Nitpick comments (1)
authme-core/src/test/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapterTest.java (1)

124-130: ⚡ Quick win

Add assertions for premium-verification delegation path.

Lines 125–130 are no-ops in the tracking adapter, so new registerPremiumVerification / unregisterPremiumVerification behavior is currently untested here. Add counters and assert calls in the lazy-init/reuse test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/test/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapterTest.java`
around lines 124 - 130, The test currently leaves the tracking adapter's
registerPremiumVerification and unregisterPremiumVerification as no-ops, so add
counters to the test adapter and assert the delegation in the lazy-init/reuse
test: modify the anonymous TrackingPlatformAdapter used in
AbstractSpigotPlatformAdapterTest to include atomic or integer counters for
registerPremiumVerification and unregisterPremiumVerification, increment those
counters inside the overridden registerPremiumVerification(DataSource,
PremiumLoginVerifier) and unregisterPremiumVerification() methods, then update
the lazy-init/reuse test to assert the expected counts after the operations that
should trigger registration and unregistration (use the same test setup that
checks other delegation counters so you reuse symbols like
registerPremiumVerification and unregisterPremiumVerification for clarity).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyBridge.java`:
- Around line 183-189: The premium.list ingestion saves mixed-case names which
later fail lowercase lookups; update the loop in BungeeProxyBridge where
premiumUsernames is populated from parsedMessage.playerName() to normalize each
entry (e.g., toLowerCase()) before adding; ensure you call
name.trim().toLowerCase() (or equivalent normalization) when adding to
premiumUsernames so lookups using lowercase names match; keep the split and
empty checks but store normalized names consistently.

In
`@authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetFreemiumAdminCommand.java`:
- Around line 19-20: The executeCommand method in SetFreemiumAdminCommand
currently calls arguments.get(0) without checking size and can throw on empty
args; add a guard at the start of SetFreemiumAdminCommand.executeCommand to
check arguments.isEmpty() (or arguments.size() < 1) and if true send a helpful
message to the CommandSender (e.g., usage or error) and return early, otherwise
call premiumService.disablePremiumAdmin(sender, arguments.get(0)).

In
`@authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java`:
- Around line 98-104: The PROXY_STARTED handler in BungeeReceiver calls
dataSource.getPremiumUsernames() synchronously (hot path) which can block
startup; change this by moving the premium list lookup off the handler: either
have CacheDataSource implement caching for getPremiumUsernames() with a periodic
refresh, or load/update the premium list asynchronously on server start and keep
it in memory, then have the PROXY_STARTED branch read the cached list (or a safe
empty fallback) and call bungeeSender.sendPremiumList(player, premiumNames);
also add error handling/logging around retrieving the cached list so failures
don't block the handshake.

In
`@authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java`:
- Around line 142-155: sendPremiumNotification currently drops messages if no
carrier is online; change it to enqueue the payload when
bukkitService.getOnlinePlayers().stream().findFirst() is empty and flush the
queue when a carrier appears. Add a thread-safe queue field (e.g.,
ConcurrentLinkedQueue<byte[]> or a small Message POJO) to BungeeSender, push the
payload into it and log a debug/info when enqueued; keep the existing behavior
of scheduling sendAuthMePluginMessage via
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask when a carrier is present.
Also add a listener or a scheduled flush method (invoked from a player-join
handler or a repeating task) that retrieves a carrier and drains the queue by
sending each queued payload with bukkitService.sendAuthMePluginMessage; ensure
thread-safety when draining the queue.

In `@authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java`:
- Around line 155-175: The verified cache only evicts expired entries on reads
via getVerifiedUuid, causing stale entries to accumulate; fix by adding
proactive cleanup when mutating or periodically: update storeVerified (and/or
constructor) to purge expired entries from the verified map before inserting by
iterating verified.entrySet() and removing entries whose
VerifiedSession.verifiedAt() is older than VERIFIED_TTL_MS, or alternatively
schedule a background task (ScheduledExecutorService) that runs periodically to
remove entries based on VERIFIED_TTL_MS; reference symbols: verified,
storeVerified, getVerifiedUuid, VERIFIED_TTL_MS, VerifiedSession.
- Around line 130-142: The supplyAsync call in PremiumLoginVerifier is using the
JVM common pool while mojangApiService.hasJoined(...) performs blocking HTTP
I/O; change CompletableFuture.supplyAsync(...) to use a dedicated Executor
(e.g., premiumVerificationExecutor) by calling CompletableFuture.supplyAsync(()
-> { ... }, premiumVerificationExecutor); add that Executor as a
constructor-injected field on PremiumLoginVerifier (or obtain from a shared
scheduler), ensure it's sized for blocking verification tasks and properly
shutdown elsewhere, and keep the existing logic involving rsaDecrypt,
computeServerHash and pend.username() inside the task unchanged.
- Around line 73-77: The pending map entries created in startVerification (which
stores PendingVerification with startedAt) are never evicted; add time-based
eviction to prevent unbounded growth by removing stale PendingVerification
entries older than a defined timeout (e.g., 60s). Implement this by (a) running
a small scheduled cleanup task (ScheduledExecutorService) that iterates the
pending map and removes entries whose PendingVerification.startedAt is older
than now - TIMEOUT, and (b) defensive-check/removal in any lookup methods (e.g.,
verify/complete methods that read from pending) to drop and treat expired
entries as absent; reference the pending map, the PendingVerification.startedAt
field, and startVerification to locate where to add timeout logic and
scheduling.

In `@authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java`:
- Around line 145-146: The auth/cache lookups use the raw playerName (e.g., in
the PlayerAuth retrieval via dataSource.getAuth and any cache lookups), but
stored keys are lowercased, so normalize the admin-supplied target before
lookups; update occurrences such as the call to dataSource.getAuth(playerName)
and any cache.get/contains calls (lines around PlayerAuth auth =
dataSource.getAuth(...), and the other spots noted) to use a consistently
lowercased form (e.g., playerName.toLowerCase(Locale.ROOT) or the project’s
canonical name-normalizer) and then proceed using that normalized variable for
auth and cache operations.

In `@authme-core/src/main/resources/messages/messages_ro.yml`:
- Around line 216-234: The translation key admin.spawn.first_not_defined was
accidentally nested under premium.admin as first_not_defined; move that entry
back to its proper namespace by removing premium.admin.first_not_defined and
adding admin.spawn.first_not_defined with the same message text ("&cPrimul spawn
a eșuat, te rugăm să încearcă să definești primul spawn.") so the locale
contains admin -> spawn -> first_not_defined instead of premium -> admin ->
first_not_defined; update only the YAML structure (no message text changes) and
keep other premium.admin keys unchanged.

In
`@authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java`:
- Around line 319-324: The current early-return in PaperDialogFlowListener that
returns true when the player's UUID is still offline (non-v4) causes a fail-open
bypass; change the branch that currently does "return true" to return false
(i.e. do not skip the blocking pre-join dialog) when player.getUniqueId() is not
the resolved/proxy-forwarded Mojang UUID, and let
AsynchronousJoin.canBypassWithPremium() perform the definitive premium ownership
check later; update the method in PaperDialogFlowListener where the comment and
"return true" appear so unresolved UUIDs block until verified.

In
`@authme-paper-common/src/main/java/fr/xephi/authme/platform/AbstractPaperPlatformAdapter.java`:
- Around line 94-107: Replace the fragile reflection in
isPaperVelocityForwardingEnabled (in AbstractPaperPlatformAdapter) that queries
io.papermc.paper.configuration.GlobalConfiguration; instead implement a stable
detection strategy: remove reliance on that internal class and either (a) read
documented server config flags (online-mode and bungeecord from spigot.yml) to
infer Velocity forwarding, or (b) perform detection at player login by
inspecting the player's connection metadata/GameProfile properties for Velocity
forwarding signatures (preferred) and expose that result to callers, or (c) fall
back to explicit config option for users to set; update any callers of
isPaperVelocityForwardingEnabled to use the new mechanism and delete the
reflection-based code path.

In
`@authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyBridge.java`:
- Around line 259-261: The current gate computes isPremiumJoin using only
proxyVerifiedPremium which allows proxy-verified players to bypass
authentication; change the logic in VelocityProxyBridge so isPremiumJoin
requires the player to be both proxyVerifiedPremium and present in
premiumUsernames (i.e., check proxyVerifiedPremium.contains(normalizedName) &&
premiumUsernames.contains(normalizedName)), update the conditional that uses
isPremiumJoin (the block with
authenticationStore.isAuthenticated(normalizedName) and logger.debug) and apply
the same fix to the analogous check around lines 264-267 so premium.unset takes
effect immediately.
- Around line 220-225: The premium list CSV is being cached without
normalization causing mismatches with the normalized checks in onPreLogin;
update the block that populates premiumUsernames (where
parsedMessage.playerName() is split) to normalize each entry (trim and
toLowerCase using the same normalization logic used in onPreLogin) before adding
to premiumUsernames so stored names match the normalized comparisons (refer to
premiumUsernames, parsedMessage.playerName(), and onPreLogin for the
normalization convention).

In `@docs/premium.md`:
- Around line 34-37: Add language identifiers to the fenced code blocks in the
premium doc to silence MD040: update the block containing "/premium    — opt in 
(must be logged in with password first) /freemium   — opt out (reverts to
password authentication)", the block containing "/authme premium <player>    —
enrol a player /authme freemium <player>   — remove a player from premium
bypass", and the block containing the "Client   Server (AuthMe + PacketEvents)  
Mojang ... AsynchronousJoin: getVerifiedUuid(name) == auth.getPremiumUuid() →
auto-login" example by changing their opening fences to include an appropriate
language tag (e.g., ```text or ```bash) so each fenced code block starts with a
language specifier.

In `@README.md`:
- Line 162: The README bullet for PacketEvents is misleadingly global; update
the line referencing "PacketEvents 2.x (optional plugin; required for inventory
protection, tab-complete blocking, and premium bypass)" to clarify that
PacketEvents is required only for direct-connection or offline-mode setups (not
for backend proxy deployments), and add a short parenthetical referencing the
"premium-bypass" section so readers know backend proxy deployments are exempt.
Ensure you modify the "PacketEvents" bullet text and, if present,
cross-reference the "premium-bypass" section to keep the docs consistent.

---

Outside diff comments:
In `@authme-core/src/main/resources/messages/messages_eu.yml`:
- Around line 218-236: Fix the Basque typo in the premium messages by changing
the value of the premium.feature_disabled entry from using "gaitu" to "gaituta",
and perform a native-speaker proofread of all keys under premium
(feature_disabled, account_not_found, already_enabled, enable_success,
not_enabled, disable_success, error) and premium.admin (not_registered,
already_enabled, account_not_found, enable_success, not_enabled,
disable_success, impostor_kicked, kick_reason) to ensure idiomatic phrasing and
correct grammar before merging.

---

Nitpick comments:
In
`@authme-core/src/test/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapterTest.java`:
- Around line 124-130: The test currently leaves the tracking adapter's
registerPremiumVerification and unregisterPremiumVerification as no-ops, so add
counters to the test adapter and assert the delegation in the lazy-init/reuse
test: modify the anonymous TrackingPlatformAdapter used in
AbstractSpigotPlatformAdapterTest to include atomic or integer counters for
registerPremiumVerification and unregisterPremiumVerification, increment those
counters inside the overridden registerPremiumVerification(DataSource,
PremiumLoginVerifier) and unregisterPremiumVerification() methods, then update
the lazy-init/reuse test to assert the expected counts after the operations that
should trigger registration and unregistration (use the same test setup that
checks other delegation counters so you reuse symbols like
registerPremiumVerification and unregisterPremiumVerification for clarity).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 734f0c0f-10ef-4590-a394-2473822f4c95

📥 Commits

Reviewing files that changed from the base of the PR and between 4d630a1 and ab16b22.

📒 Files selected for processing (102)
  • README.md
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/AuthMeBungeePlugin.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyBridge.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyConfiguration.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/config/BungeeConfigProperties.java
  • authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeProxyBridgeTest.java
  • authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeReloadCommandTest.java
  • authme-core/src/main/java/fr/xephi/authme/command/CommandInitializer.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetFreemiumAdminCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetPremiumAdminCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/premium/FreemiumCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/premium/PremiumCommand.java
  • authme-core/src/main/java/fr/xephi/authme/data/auth/PlayerAuth.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/AbstractSqlDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/Columns.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/DataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/MySQL.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/SQLite.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/AesCfb8Decoder.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/AesCfb8Encoder.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsListenerRegistry.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsService.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java
  • authme-core/src/main/java/fr/xephi/authme/message/MessageKey.java
  • authme-core/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java
  • authme-core/src/main/java/fr/xephi/authme/permission/AdminPermission.java
  • authme-core/src/main/java/fr/xephi/authme/permission/PlayerPermission.java
  • authme-core/src/main/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapter.java
  • authme-core/src/main/java/fr/xephi/authme/platform/PacketInterceptionAdapter.java
  • authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
  • authme-core/src/main/java/fr/xephi/authme/service/MojangApiService.java
  • authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java
  • authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/PremiumSettings.java
  • authme-core/src/main/resources/messages/messages_bg.yml
  • authme-core/src/main/resources/messages/messages_br.yml
  • authme-core/src/main/resources/messages/messages_cz.yml
  • authme-core/src/main/resources/messages/messages_de.yml
  • authme-core/src/main/resources/messages/messages_en.yml
  • authme-core/src/main/resources/messages/messages_eo.yml
  • authme-core/src/main/resources/messages/messages_es.yml
  • authme-core/src/main/resources/messages/messages_et.yml
  • authme-core/src/main/resources/messages/messages_eu.yml
  • authme-core/src/main/resources/messages/messages_fi.yml
  • authme-core/src/main/resources/messages/messages_fr.yml
  • authme-core/src/main/resources/messages/messages_gl.yml
  • authme-core/src/main/resources/messages/messages_hu.yml
  • authme-core/src/main/resources/messages/messages_id.yml
  • authme-core/src/main/resources/messages/messages_it.yml
  • authme-core/src/main/resources/messages/messages_ja.yml
  • authme-core/src/main/resources/messages/messages_ko.yml
  • authme-core/src/main/resources/messages/messages_lt.yml
  • authme-core/src/main/resources/messages/messages_nl.yml
  • authme-core/src/main/resources/messages/messages_pl.yml
  • authme-core/src/main/resources/messages/messages_pt.yml
  • authme-core/src/main/resources/messages/messages_ro.yml
  • authme-core/src/main/resources/messages/messages_ru.yml
  • authme-core/src/main/resources/messages/messages_si.yml
  • authme-core/src/main/resources/messages/messages_sk.yml
  • authme-core/src/main/resources/messages/messages_sr.yml
  • authme-core/src/main/resources/messages/messages_tr.yml
  • authme-core/src/main/resources/messages/messages_uk.yml
  • authme-core/src/main/resources/messages/messages_vn.yml
  • authme-core/src/main/resources/messages/messages_zhcn.yml
  • authme-core/src/main/resources/messages/messages_zhhk.yml
  • authme-core/src/main/resources/messages/messages_zhmc.yml
  • authme-core/src/main/resources/messages/messages_zhtw.yml
  • authme-core/src/main/resources/plugin.yml
  • authme-core/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java
  • authme-core/src/test/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapterTest.java
  • authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java
  • authme-core/src/test/java/fr/xephi/authme/service/bungeecord/BungeeReceiverTest.java
  • authme-core/src/test/resources/fr/xephi/authme/datasource/sql-initialize.sql
  • authme-core/src/test/resources/plugin.yml
  • authme-folia/src/main/resources/plugin.yml
  • authme-paper-common/pom.xml
  • authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java
  • authme-paper-common/src/main/java/fr/xephi/authme/platform/AbstractPaperPlatformAdapter.java
  • authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java
  • authme-paper/src/main/resources/plugin.yml
  • authme-spigot-1.21/src/main/resources/plugin.yml
  • authme-spigot-legacy/src/main/resources/plugin.yml
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/AuthMeVelocityPlugin.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyBridge.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyConfiguration.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/config/VelocityConfigProperties.java
  • authme-velocity/src/test/java/fr/xephi/authme/velocity/VelocityProxyBridgeTest.java
  • docs/commands.md
  • docs/config.md
  • docs/permission_nodes.md
  • docs/premium.md
  • docs/proxies/bungee/config.yml
  • docs/proxies/velocity/config.yml
  • docs/translations.md
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.42.1)
authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java

[warning] 183-183: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)


[warning] 177-177: Cipher in ECB mode is detected. ECB mode produces the same output for the same input each time which allows an attacker to intercept and replay the data. Further, ECB mode does not provide any integrity checking. See https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY.
Context: Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
Note: [CWE-327] Use of a Broken or Risky Cryptographic Algorithm. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(ecb-cipher-java)

🪛 LanguageTool
docs/permission_nodes.md

[style] ~31-~31: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...mand to set the first AuthMe spawn. - authme.admin.setfreemium – Administrator com...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~32-~32: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... disable premium mode for a player. - authme.admin.setpremium – Administrator comm...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~67-~67: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ssion to see the own email address. - authme.player.freemium – Permission to disab...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~68-~68: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...Permission to disable premium mode. - authme.player.login – Command permission to ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~69-~69: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...in** – Command permission to login. - authme.player.logout – Command permission to...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~69-~69: Ensure spelling is correct
Context: ...player.logout** – Command permission to logout. - authme.player.premium – Permissi...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~70-~70: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...t** – Command permission to logout. - authme.player.premium – Permission to enable...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.22.1)
docs/premium.md

[warning] 34-34: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 42-42: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 54-54: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (56)
authme-core/src/main/resources/messages/messages_zhhk.yml (1)

221-238: Looks good.

The new premium/admin strings fit the existing locale structure and cover the feature’s player/admin flows cleanly.

authme-core/src/main/resources/messages/messages_sr.yml (1)

218-235: Looks good.

The new premium/admin translations are consistent with the existing message grouping and cover the added command paths.

authme-core/src/main/resources/messages/messages_tr.yml (1)

216-233: Looks good.

The premium/admin strings are wired into the locale file consistently and match the new feature surface.

authme-core/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java (1)

141-144: Looks good.

The new MYSQL_COL_PREMIUM_UUID setting follows the existing database property pattern and cleanly exposes the premium UUID column name.

authme-core/src/main/java/fr/xephi/authme/datasource/Columns.java (1)

34-34: Looks good.

PREMIUM_UUID is exposed and populated consistently with the rest of the configurable column mappings.

Also applies to: 58-58

authme-core/src/main/java/fr/xephi/authme/permission/PlayerPermission.java (1)

86-96: Looks good.

The new premium permission nodes and updated enum terminator are consistent with the existing permission model.

authme-core/src/main/resources/messages/messages_en.yml (1)

216-233: Looks good.

The premium/admin message keys are added in the expected place and line up with the new command flow.

authme-paper-common/pom.xml (1)

64-68: Dependency is correctly managed. The ch.jalu:datasourcecolumns dependency is defined in the parent POM's dependencyManagement section (pom.xml:534-535), so omitting the version here is correct.

authme-bungee/src/main/java/fr/xephi/authme/bungee/config/BungeeConfigProperties.java (1)

55-60: Looks good.

The new LOGIN_SERVER property fits the existing ConfigMe pattern and keeps the redirect target configurable without affecting the rest of the proxy settings.

authme-core/src/main/resources/messages/messages_de.yml (1)

218-235: Looks good.

The new premium block is wired in cleanly and covers the added premium/admin message keys without disturbing the existing translations.

authme-core/src/main/resources/messages/messages_fi.yml (1)

218-235: Looks good.

The Finnish premium section is added cleanly and aligns with the new message keys introduced by the premium flow.

authme-core/src/main/resources/messages/messages_vn.yml (1)

218-235: Looks good.

The new Vietnamese premium block covers the added user/admin messages and fits the existing localization structure.

authme-core/src/main/resources/messages/messages_eo.yml (1)

218-235: Looks good.

The Esperanto premium block is added in the right place and covers the new premium/admin keys cleanly.

authme-core/src/main/resources/messages/messages_lt.yml (1)

218-235: Looks good.

The Lithuanian premium block lines up with the new namespace and introduces the new premium/admin messages cleanly.

authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeProxyBridgeTest.java (2)

147-159: Looks good.

The logout redirect test now matches the updated BungeeProxyConfiguration constructor signature.


441-444: Looks good.

The shared test helper now supplies the new loginServer argument, so the rest of the test cases stay aligned with the configuration API.

authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyConfiguration.java (1)

24-60: All constructor invocations have been correctly updated with the new loginServer argument in position 11, immediately preceding sharedSecret in position 12. No issues found.

authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeReloadCommandTest.java (1)

29-38: Constructor call matches the updated config shape.

The test still exercises the same reload path, and the extra argument lines up with the new BungeeProxyConfiguration signature.

authme-core/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java (1)

10-13: Premium message IDs are wired in cleanly.

The new enum constants follow the existing pattern, and fromId() still works the same way.

authme-core/src/main/java/fr/xephi/authme/permission/AdminPermission.java (1)

141-151: New admin permission nodes look consistent.

The premium/freemium entries follow the existing enum shape and naming scheme.

authme-core/src/main/java/fr/xephi/authme/data/auth/PlayerAuth.java (1)

47-48: Premium UUID plumbing is consistent.

The new field, accessor/mutator, isPremium() helper, and builder assignment all line up cleanly.

Also applies to: 187-197, 250-277, 390-393

authme-core/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java (1)

56-59: Premium UUID column mapping looks correct.

The new column definition matches PlayerAuth#getPremiumUuid() and stays optional.

authme-core/src/main/java/fr/xephi/authme/datasource/MySQL.java (1)

297-300: MySQL schema and row mapping are in sync.

The new premium_uuid column creation and result-set hydration line up with the PlayerAuth model update.

Also applies to: 498-519

authme-core/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java (1)

259-262: PostgreSQL schema and row mapping are aligned.

The new premium_uuid column setup and premiumUuid(...) builder wiring match the new auth model field.

Also applies to: 460-480

authme-core/src/main/java/fr/xephi/authme/datasource/SQLite.java (1)

201-220: No issue found; the migration path properly preserves the premium_uuid column.

reload() invokes setup() before data migration (line 97 of SqLiteMigrater), ensuring the new table is created with all current columns including PREMIUM_UUID. The subsequent INSERT statement copies legacy columns only because migrated databases predate this column. The PREMIUM_UUID column structure is preserved; rows simply contain NULL values as expected for newly added columns on old data.

authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java (1)

42-50: Constructor change is correctly wired everywhere.

All BungeeReceiver instantiations (2 found in test files) have been updated to pass the DataSource parameter. No compilation issues.

authme-core/src/main/resources/messages/messages_ru.yml (1)

213-230: LGTM — the new premium localization block fits the existing message structure.

The added premium and premium.admin keys look complete and consistent with the surrounding locale file.

authme-core/src/main/resources/messages/messages_pl.yml (1)

218-235: LGTM — the premium messages are wired in cleanly.

The new section is structurally sound and the added keys cover the expected user/admin premium flows.

authme-core/src/main/resources/messages/messages_hu.yml (1)

218-235: LGTM — no issues in the new premium block.

The locale additions are consistent with the surrounding YAML and appear complete for the premium/admin messages.

authme-core/src/main/resources/messages/messages_gl.yml (1)

218-235: LGTM — the new premium localization entries look good.

The added keys line up with the intended premium feature flow and don’t introduce any YAML structure problems.

authme-core/src/main/resources/messages/messages_zhcn.yml (1)

218-235: LGTM — the premium message additions are consistent.

The new top-level premium section and nested admin keys integrate cleanly with the existing message tree.

authme-core/src/main/resources/messages/messages_uk.yml (1)

218-235: LGTM — the premium localization block looks complete.

The new entries match the surrounding file’s structure and cover the expected premium/admin states.

authme-core/src/main/resources/messages/messages_zhtw.yml (1)

220-237: LGTM — the added premium section is well-formed.

The new keys appear consistent with the locale file’s conventions and should support the premium flows cleanly.

authme-core/src/main/resources/messages/messages_bg.yml (1)

217-234: LGTM — no concerns with the new premium messages.

The section is structurally correct and the added strings cover the player/admin premium paths as expected.

authme-core/src/main/resources/messages/messages_it.yml (1)

219-237: Premium message block looks consistent and complete.

Key coverage and YAML structure for premium / premium.admin look good.

authme-core/src/main/resources/messages/messages_nl.yml (1)

218-236: Dutch premium translations are well-structured.

The new premium namespace appears correctly formed and aligned with expected key patterns.

authme-core/src/main/resources/messages/messages_si.yml (1)

218-236: Premium keys are correctly added for this locale.

Structure and placeholder usage are consistent with the rest of the file.

authme-core/src/main/resources/messages/messages_ja.yml (1)

217-234: Japanese premium message additions look good.

The new keys are complete and consistently structured.

authme-core/src/main/resources/messages/messages_pt.yml (1)

218-236: Portuguese premium translations are clean and complete.

The added keyset is well-formed and consistent with the message schema.

authme-core/src/main/java/fr/xephi/authme/service/MojangApiService.java (1)

42-119: Solid baseline for Mojang API integration.

Error handling, timeout configuration, and safe UUID parsing paths are implemented cleanly.

docs/proxies/bungee/config.yml (1)

39-45: Looks good.

The new loginServer entry and comments match the intended redirect behavior and fit the existing generated config structure.

authme-core/src/main/resources/messages/messages_id.yml (1)

218-235: Looks good.

The new premium message keys and admin subkeys are consistent with the feature’s command and service flow.

docs/translations.md (1)

2-2: Looks consistent.

The refreshed translation percentages and generation timestamps appear to be an expected auto-generated update.

Also applies to: 15-15, 19-21, 31-31, 34-34, 41-41, 47-47

authme-core/src/main/resources/messages/messages_cz.yml (1)

218-235: Looks good.

The premium message additions are aligned with the new command/service paths and read naturally for the Czech locale.

authme-core/src/main/resources/messages/messages_es.yml (1)

219-236: Looks good.

The new premium translations cover the expected user/admin flows and fit the existing message structure.

authme-velocity/src/main/java/fr/xephi/authme/velocity/config/VelocityConfigProperties.java (1)

55-60: Looks good.

The new LOGIN_SERVER property cleanly exposes the redirect target and preserves the existing default-disabled behavior.

docs/permission_nodes.md (1)

2-2: Looks good.

The new premium permission nodes line up with the added commands and are reflected correctly in the generated docs.

Also applies to: 31-32, 67-70, 83-83

authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetPremiumAdminCommand.java (1)

19-20: Remove this comment.

The command framework enforces argument validation before executeCommand is invoked. CommandMapper.mapPartsToCommand() validates that the argument count matches the command's definition using hasSuitableArgumentCount(). If the argument count is invalid, it returns INCORRECT_ARGUMENTS status, and CommandHandler sends an error message without executing the command. Since SetPremiumAdminCommand is registered with one mandatory argument, /authme setpremium without a player name will be rejected by the framework before reaching this code. The arguments.get(0) call is safe.

authme-core/src/main/java/fr/xephi/authme/listener/packetevents/AesCfb8Encoder.java (1)

24-27: ⚠️ Potential issue | 🟡 Minor

Guard against null cipher output before writing

The Cipher.update(byte[]) method returns null for zero-length input (per Java SE documentation). If an empty buffer reaches this encoder, line 26 will attempt to write null bytes, causing a NullPointerException.

Suggested fix
     protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) {
         byte[] input = new byte[in.readableBytes()];
         in.readBytes(input);
+        byte[] encrypted = cipher.update(input);
+        if (encrypted != null) {
+            out.writeBytes(encrypted);
+        }
     }
			> Likely an incorrect or invalid review comment.
authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyConfiguration.java (1)

45-46: The LOGIN_SERVER property has a default value of "" and normalizeServerName() safely handles empty strings—it simply returns the trimmed and lowercased input. Empty server names are also explicitly checked for at usage time (VelocityProxyBridge line 499), indicating they are expected and valid. No startup failure risk exists for missing or blank values.

			> Likely an incorrect or invalid review comment.
authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java (1)

220-225: Good race-condition guard before limbo/dialog scheduling.

The early and in-task playerCache.isAuthenticated(...) checks are a solid fix for the proxy timing window and should prevent permanent limbo freezes.

Also applies to: 253-256

authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsService.java (1)

56-60: Premium verification listener lifecycle is well handled.

The gating and unregister paths are clean, and disabling/unloading correctly tears down premium verification hooks.

Also applies to: 103-111, 126-129

authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java (1)

297-459: Strong coverage for premium pre-join bypass logic.

Nice additions here: verified/unverified paths, proxy UUID variants, and impostor mismatch behavior are all covered, which should protect the new bypass flow from regressions.

authme-core/src/test/resources/plugin.yml (1)

24-25: Command and permission wiring looks consistent.

The new premium/freemium command and permission nodes are correctly reflected in command usage, top-level command entries, and wildcard permission children.

Also applies to: 64-69, 94-95, 163-168, 269-320

authme-folia/src/main/resources/plugin.yml (1)

43-44: Folia plugin metadata updates look good.

The premium/freemium command exposure and permission tree updates are coherent and aligned with the rest of the descriptor.

Also applies to: 83-88, 113-114, 182-187, 288-339

authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java (1)

82-95: No thread-affinity issue here; the code is correct as-is.

PacketEvents explicitly documents that User.sendPacket() is thread-safe and can be safely called from arbitrary async threads without marshalling to the channel event loop. The underlying Netty operations handle thread-safety internally. The proposed runOnEventLoop() wrapper pattern is unnecessary and adds unnecessary complexity.

			> Likely an incorrect or invalid review comment.

Comment thread authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyBridge.java Outdated
Comment thread authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java Outdated
Comment on lines +142 to +155
private void sendPremiumNotification(MessageType type, String username) {
if (!isEnabled || !plugin.isEnabled()) {
return;
}
Optional<? extends Player> carrier = bukkitService.getOnlinePlayers().stream().findFirst();
carrier.ifPresent(p -> {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF(type.getId());
out.writeUTF(username.toLowerCase(java.util.Locale.ROOT));
byte[] payload = out.toByteArray();
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() ->
bukkitService.sendAuthMePluginMessage(p, payload));
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't drop premium updates when no carrier is online.

sendPremiumNotification() silently returns when the server is empty, so /premium and /freemium changes can be persisted locally but never reach the proxy. That leaves proxy state stale until some later full resync.

Queue the message until a carrier exists, or at least log/retry so the update is not lost.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java`
around lines 142 - 155, sendPremiumNotification currently drops messages if no
carrier is online; change it to enqueue the payload when
bukkitService.getOnlinePlayers().stream().findFirst() is empty and flush the
queue when a carrier appears. Add a thread-safe queue field (e.g.,
ConcurrentLinkedQueue<byte[]> or a small Message POJO) to BungeeSender, push the
payload into it and log a debug/info when enqueued; keep the existing behavior
of scheduling sendAuthMePluginMessage via
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask when a carrier is present.
Also add a listener or a scheduled flush method (invoked from a player-join
handler or a repeating task) that retrieves a carrier and drains the queue by
sending each queued payload with bukkitService.sendAuthMePluginMessage; ensure
thread-safety when draining the queue.

Comment on lines +94 to +107
private static boolean isPaperVelocityForwardingEnabled() {
try {
// io.papermc.paper.configuration.GlobalConfiguration is Paper 1.18+
Class<?> cfg = Class.forName(
"io.papermc.paper.configuration.GlobalConfiguration",
false, AbstractPaperPlatformAdapter.class.getClassLoader());
Object instance = cfg.getMethod("get").invoke(null);
Object proxies = cfg.getField("proxies").get(instance);
Object velocity = proxies.getClass().getField("velocity").get(proxies);
Object enabled = velocity.getClass().getField("enabled").get(velocity);
return Boolean.TRUE.equals(enabled);
} catch (Exception ignored) {
return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For currently supported Paper versions, does io.papermc.paper.configuration.GlobalConfiguration reliably expose get().proxies.velocity.enabled as public reflective fields? If not, what stable API/config path is recommended to detect Velocity forwarding?

💡 Result:

No, io.papermc.paper.configuration.GlobalConfiguration does not reliably expose get.proxies.velocity.enabled as public reflective fields. While the class exists in io.papermc.paper.configuration and provides a static get method returning a singleton instance with nested public fields matching the paper-global.yml structure (including proxies.velocity.enabled), this is an internal Paper implementation detail using Configurate's ConfigurationPart system for automatic YAML serialization/deserialization. It is not part of the stable public Paper/Spigot/Bukkit API and should not be used by plugins, as it lacks versioning guarantees, may change between builds/versions, and is not documented in official JavaDocs as a stable API. The recommended stable way to detect Velocity forwarding in plugins is to check player connection metadata or forwarding headers provided by Velocity's modern forwarding protocol. When enabled, Velocity injects specific player info (UUID, IP, skin) via a custom login packet that Paper recognizes. Plugins can detect this by: 1. Checking if the server's online-mode is false (required for forwarding) and bungeecord is false in spigot.yml (to distinguish from BungeeCord). 2. Using ServerPlayer#getRemoteAddress or connection metadata, as Velocity sets the real client IP in the forwarded data. 3. Listening for login events and inspecting GameProfile properties, which contain forwarded data when Velocity is active. For runtime detection without config access, inspect if player connections have Velocity-specific forwarding signatures (e.g., extra data in Login packet). Official docs emphasize config.path proxies.velocity.enabled for server admins, but plugins should use protocol/runtime detection for compatibility.

Citations:


🏁 Script executed:

cd authme-paper-common && rg "isPaperVelocityForwardingEnabled" -A 5 -B 5

Repository: TuxCoding/AuthMeReloaded

Length of output: 2501


🏁 Script executed:

cd authme-paper-common && cat -n src/main/java/fr/xephi/authme/platform/AbstractPaperPlatformAdapter.java | sed -n '90,115p'

Repository: TuxCoding/AuthMeReloaded

Length of output: 1031


Velocity forwarding detection relies on an unstable internal API that will fail silently.

io.papermc.paper.configuration.GlobalConfiguration is not a stable public API and lacks versioning guarantees—it may change between builds/versions. Any reflection failure on line 106 returns false, which incorrectly marks Velocity as disabled and breaks logins in proxied setups. Instead, use documented approaches: (1) check online-mode: false and bungeecord: false in spigot.yml to confirm Velocity forwarding mode, (2) inspect player connection metadata at login time to detect forwarded connections, or (3) examine GameProfile properties for Velocity-specific forwarding signatures. These stable methods are compatible across versions and avoid silent failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-paper-common/src/main/java/fr/xephi/authme/platform/AbstractPaperPlatformAdapter.java`
around lines 94 - 107, Replace the fragile reflection in
isPaperVelocityForwardingEnabled (in AbstractPaperPlatformAdapter) that queries
io.papermc.paper.configuration.GlobalConfiguration; instead implement a stable
detection strategy: remove reliance on that internal class and either (a) read
documented server config flags (online-mode and bungeecord from spigot.yml) to
infer Velocity forwarding, or (b) perform detection at player login by
inspecting the player's connection metadata/GameProfile properties for Velocity
forwarding signatures (preferred) and expose that result to callers, or (c) fall
back to explicit config option for users to set; update any callers of
isPaperVelocityForwardingEnabled to use the new mechanism and delete the
reflection-based code path.

Comment on lines +220 to +225
premiumUsernames.clear();
if (!parsedMessage.playerName().isEmpty()) {
for (String name : parsedMessage.playerName().split(",")) {
if (!name.isEmpty()) {
premiumUsernames.add(name.trim());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize premium.list entries before caching.

onPreLogin checks normalized usernames, but premium.list currently stores raw CSV names. Mixed-case entries can silently fail to match.

Suggested fix
-                for (String name : parsedMessage.playerName().split(",")) {
-                    if (!name.isEmpty()) {
-                        premiumUsernames.add(name.trim());
+                for (String name : parsedMessage.playerName().split(",")) {
+                    String trimmed = name.trim();
+                    if (!trimmed.isEmpty()) {
+                        premiumUsernames.add(normalizeName(trimmed));
                     }
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyBridge.java`
around lines 220 - 225, The premium list CSV is being cached without
normalization causing mismatches with the normalized checks in onPreLogin;
update the block that populates premiumUsernames (where
parsedMessage.playerName() is split) to normalize each entry (trim and
toLowerCase using the same normalization logic used in onPreLogin) before adding
to premiumUsernames so stored names match the normalized comparisons (refer to
premiumUsernames, parsedMessage.playerName(), and onPreLogin for the
normalization convention).

Comment on lines +259 to +261
boolean isPremiumJoin = connectingToAuthServer && proxyVerifiedPremium.contains(normalizedName);
if (!authenticationStore.isAuthenticated(normalizedName) && !isPremiumJoin) {
logger.debug("Skipping auto-login for {} — not authenticated or proxy-verified premium", normalizedName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Gate proxy premium bypass on premium enrollment, not only online-mode verification.

isPremiumJoin trusts proxyVerifiedPremium alone. That allows bypass as soon as a player is proxy-verified, and it makes premium.unset ineffective until disconnect. Require explicit membership in premiumUsernames in this gate.

Suggested fix
-        boolean isPremiumJoin = connectingToAuthServer && proxyVerifiedPremium.contains(normalizedName);
+        boolean isPremiumJoin = connectingToAuthServer
+            && proxyVerifiedPremium.contains(normalizedName)
+            && premiumUsernames.contains(normalizedName);

Also applies to: 264-267

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyBridge.java`
around lines 259 - 261, The current gate computes isPremiumJoin using only
proxyVerifiedPremium which allows proxy-verified players to bypass
authentication; change the logic in VelocityProxyBridge so isPremiumJoin
requires the player to be both proxyVerifiedPremium and present in
premiumUsernames (i.e., check proxyVerifiedPremium.contains(normalizedName) &&
premiumUsernames.contains(normalizedName)), update the conditional that uses
isPremiumJoin (the block with
authenticationStore.isAuthenticated(normalizedName) and logger.debug) and apply
the same fix to the analogous check around lines 264-267 so premium.unset takes
effect immediately.

Comment thread docs/premium.md Outdated
Comment thread README.md
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
authme-core/src/main/resources/messages/messages_et.yml (1)

1-238: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Encoding corruption (Mojibake) introduced across ~50+ changed Estonian message lines.

The file was edited in a Latin-1-mode environment: UTF-8 multibyte sequences were decoded as Latin-1 and then re-saved as UTF-8, producing double-encoded sequences. Examples from the changed lines:

Garbled (in file) Should be
käsklusega käsklusega
mängijad mängijad
Tühista Tühista
päev päev
Premium-režiim Premium-režiim
Mängija, kes oli ühendatud Mängija, kes oli ühendatud

Unchanged lines (e.g., command_usage, match_error) are unaffected, confirming this is a PR-introduced regression.

Fix: Revert all changes to existing pre-premium keys, then re-add only the premium block (lines 217–238) using a UTF-8-aware editor. A quick Python one-liner can verify/restore corrupted characters: s.encode('latin-1').decode('utf-8').

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@authme-core/src/main/resources/messages/messages_et.yml` around lines 1 -
238, Many Estonian strings were double-encoded (mojibake) in keys like
registration.register_request, password.match_error, dialog.button.cancel,
time.day and others; revert all edits to the pre-existing keys (everything
except the premium block) so their original UTF-8 text is restored, then re-add
only the premium block (premium.feature_disabled ... premium.pending_fail)
ensuring the file is saved in UTF-8; open/save with a UTF-8-aware editor or run
a transliteration fix on the corrupted values (e.g. for a string s:
s.encode('latin-1').decode('utf-8')) to convert garbled entries back to proper
UTF-8 before committing.
authme-core/src/main/resources/messages/messages_br.yml (1)

1-241: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same Mojibake encoding corruption as messages_et.yml — affects Brazilian Portuguese messages in-game.

Numerous changed lines have double-encoded UTF-8, making Portuguese-specific characters unreadable. Examples:

Garbled (in file) Should be
Tradução Tradução
usuários usuários
Você Você
não não
reconecte-sereconecte-é-se variants correct accented form

Unchanged lines (e.g., Line 11, Line 29) are unaffected, confirming the corruption was introduced by this PR's edits. The premium block (Lines 222–241) is also affected.

Fix: Same as for messages_et.yml — revert all changes to pre-existing keys and re-add only the premium block using a UTF-8-aware editor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@authme-core/src/main/resources/messages/messages_br.yml` around lines 1 -
241, This file contains double-encoded UTF-8 (mojibake) in many translation
values; revert the edited translations back to their original pre-PR Portuguese
strings for keys like registration.register_request, password.match_error,
login.login_request, error.unregistered_user,
on_join_validation.invalid_name_case, recovery.code.code_sent, dialog.* and
other top-level sections, then re-add only the premium block (premium.* keys)
using a UTF-8-aware editor and save the file as UTF-8 (no BOM) so accented
characters (e.g., Tradução, usuários, Você, não, reconecte-se) are stored
correctly; ensure no other keys are changed beyond restoring correct Portuguese
text.
♻️ Duplicate comments (3)
docs/premium.md (1)

54-79: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the markdown linting fix: add language tag to this code block.

The past review flagged three code blocks (lines 34, 42, 54) for missing language tags. Only the first two were fixed — this block still triggers the MD040 warning.

📝 Proposed fix
-```
+```text
 Client                         Server (AuthMe + PacketEvents)        Mojang
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/premium.md` around lines 54 - 79, Add a language tag to the fenced code
block that begins with "Client                         Server (AuthMe +
PacketEvents)        Mojang" so the markdown linter (MD040) is satisfied; change
the opening fence from ``` to ```text (or another appropriate language such as
```nohighlight) and leave the block contents unchanged.
authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java (1)

158-160: ⚠️ Potential issue | 🟠 Major

Normalize the admin target before storage/cache lookups.

These paths still use raw playerName for dataSource.getAuth(...) and playerCache.isAuthenticated(...). Mixed-case /authme premium|freemium <player> calls can miss the stored auth record or skip the cache refresh even though the account exists. Normalize once and use that key for persistence/cache operations.

Also applies to: 228-249

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java` around
lines 158 - 160, Normalize the admin target once at the start of the async task
and use that normalized key for all persistence/cache calls: replace uses of the
raw playerName inside the bukkitService.runTaskOptionallyAsync lambda (where
dataSource.getAuth(playerName) and playerCache.isAuthenticated(playerName) are
called) with a single normalized variable (e.g., normalizedPlayer or
normalizedName) and use that same variable for any subsequent lookups/updates;
apply the same change to the other block referenced (the logic around lines
228-249) so all dataSource and playerCache interactions consistently use the
normalized key.
authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java (1)

89-103: ⚠️ Potential issue | 🟠 Major

Enforce PENDING_TTL_MS on reads, not just on new starts.

evictStalePendingEntries() only runs from startVerification(). An expired handshake can still pass hasPending(...), return a username/UUID, and be accepted by completeVerification(...) long after the 30s window if no newer premium start happened. Expiry needs to be checked when reading/removing pending entries too.

Also applies to: 133-138

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java`
around lines 89 - 103, The pending-entry TTL is only enforced when starting
verifications, so reads can return stale entries; update all read/remove methods
(e.g., hasPending, getPendingUsername, getPendingPlayerUUID and the methods
around lines 133-138 such as completeVerification/removePending) to check expiry
before returning data. Implement this by either invoking
evictStalePendingEntries() at the start of each read/remove method or by
checking the PendingVerification's timestamp against PENDING_TTL_MS (and
removing the map entry if expired) so expired handshakes are treated as absent
and not returned or accepted.
🧹 Nitpick comments (4)
docs/premium.md (1)

183-187: ⚖️ Poor tradeoff

Clarify the account re-enrollment scenario after username change.

The phrase "depending on your account-linking configuration" is vague. Since AuthMe accounts are name-keyed and the premium bypass uses Mojang UUID, readers need concrete guidance:

  • Will the old account (old name) still auto-login using the stored UUID?
  • Must the player register a new account with the new name and re-enroll?
  • Is there a migration path?

Consider expanding this answer to describe the actual behavior users will encounter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/premium.md` around lines 183 - 187, Clarify that premium bypass is
checked by Mojang UUID while AuthMe stores accounts by lowercase player name, so
after a username change the premium check will still recognize the player
(UUID), but AuthMe will not automatically map the new name to the old name-keyed
account; instruct readers to either run /authme premium to re-link the new name
to their premium status or use their server's AuthMe account-migration/rename
feature (or a plugin setting that enables UUID-based account linking) to migrate
the old name record to the new lowercase name so the player does not need to
fully re-register.
authme-core/src/main/resources/plugin.yml (1)

326-338: ⚡ Quick win

Consider documenting the implications of default: true for authme.player.premium.

Both authme.player.premium and authme.player.freemium default to true, meaning every player on the server can invoke /premium (and thereby attempt premium enrollment) without any explicit admin grant. This is consistent with all other authme.player.* defaults, and the actual bypass is protected by cryptographic Mojang session verification — so there is no direct auth-bypass risk.

However, server admins may not realize that any player can self-enroll without admin intervention. A short note in the description or in the accompanying documentation (e.g., README / wiki) clarifying that admins must explicitly deny this node to restrict self-enrollment would prevent deployment surprises.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@authme-core/src/main/resources/plugin.yml` around lines 326 - 338, The
permission node authme.player.premium currently has default: true (as does
authme.player.freemium), which means any player can run /premium to attempt
premium enrollment; update the authme.player.premium description (or
README/wiki) to explicitly state that default:true allows all players to
self-enroll and that server operators must explicitly deny this permission to
prevent self-enrollment, and mention that enrollment is still gated by Mojang
session verification for cryptographic protection.
authme-core/src/test/java/fr/xephi/authme/service/bungeecord/BungeeReceiverTest.java (1)

99-127: ⚡ Quick win

Test does not cover the premium verification flow.

The test stubs the necessary mocks for the basic PERFORM_LOGIN flow, but pendingPremiumCache returns null by default, so the premium verification branch (lines 164-176 in BungeeReceiver) is never exercised. Consider adding test cases that:

  1. Verify behavior when a pending premium UUID exists and matches the player's v4 UUID
  2. Verify behavior when the pending UUID exists but the player has a v3 UUID (offline mode)
  3. Verify behavior when the pending UUID doesn't match the player's UUID
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/test/java/fr/xephi/authme/service/bungeecord/BungeeReceiverTest.java`
around lines 99 - 127, Add tests that exercise the premium verification branch
by stubbing a pending premium UUID and the player's UniqueId: create three new
test cases (modeled on
shouldQueueSessionAndForceLoginWhenPerformLoginReceivedForOnlinePlayer) where
you set pendingPremiumCache to return a UUID for playerName, then set the mocked
Player.getUniqueId() to (a) a matching v4 UUID, (b) a v3/offline-mode UUID, and
(c) a non-matching UUID; in each test call receiver.onPluginMessageReceived(...)
and verify the expected interactions with
proxySessionManager.processProxySessionMessage, management.forceLoginFromProxy
and bungeeSender.sendAuthMeBungeecordMessage and also verify pendingPremiumCache
removal or premiumService invocation as appropriate to each branch so the
premium verification logic in BungeeReceiver is covered.
authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java (1)

97-111: ⚡ Quick win

LGTM with a suggestion for additional test coverage.

The PremiumLoginVerifier mock is added and ENABLE_PREMIUM is stubbed to false in setUp(), which correctly disables the premium bypass path for existing non-premium tests. However, this means the new canBypassWithPremium() logic in AsynchronousJoin is not exercised.

Consider adding test cases with ENABLE_PREMIUM set to true to cover:

  1. Premium bypass with a v4 UUID (proxy-verified)
  2. Premium bypass with a v3 UUID requiring PremiumLoginVerifier verification
  3. Pending premium verification success/failure paths
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java`
around lines 97 - 111, The test setup stubs PremiumSettings.ENABLE_PREMIUM to
false so AsynchronousJoin.canBypassWithPremium() is never exercised; add new
unit tests in AsynchronousJoinTest that set
given(service.getProperty(PremiumSettings.ENABLE_PREMI)).willReturn(true) and
exercise AsynchronousJoin.canBypassWithPremium() for (1) a v4 UUID path that
should bypass immediately (proxy-verified), (2) a v3 UUID path that invokes the
PremiumLoginVerifier mock (verify interactions with PremiumLoginVerifier) and
(3) pending verification flows where the verifier returns success and failure to
assert both acceptance and denial handling; update/instantiate the
PremiumLoginVerifier mock behavior in each test and reuse existing helper
methods from setUp() to schedule tasks as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyBridge.java`:
- Around line 331-357: The onLogin and onPostLogin handlers currently mark every
online-mode or UUIDv4 player as proxy verified; change both to first check that
the normalizedName is present in premiumUsernames or pendingPremiumUsernames
before calling markProxyVerifiedPremium or adding to proxyVerifiedPremium. In
onLogin, after computing normalizedName call a membership check against
premiumUsernames || pendingPremiumUsernames and only then call
markProxyVerifiedPremium(normalizedName); in onPostLogin, compute
normalizedName, verify membership in premiumUsernames || pendingPremiumUsernames
and only then perform proxyVerifiedPremium.add(normalizedName) and log the
Mojang UUID message. Ensure you reference the existing symbols normalizedName,
markProxyVerifiedPremium, proxyVerifiedPremium, premiumUsernames,
pendingPremiumUsernames, onLogin and onPostLogin when making the change.

In
`@authme-core/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java`:
- Around line 44-52: The pre-join branch calls
preJoinDialogService.approvePreJoinForceLogin(...) without checking the target's
CAN_LOGIN_BE_FORCED permission, so add a permission check before approving:
query the permission/authorization routine used elsewhere (e.g.,
PermissionNode.CAN_LOGIN_BE_FORCED or the same permission-check helper used for
online players) for playerName.toLowerCase(Locale.ROOT) and only call
preJoinDialogService.approvePreJoinForceLogin(...) if that check allows forced
logins; otherwise treat as denied and send the existing failure message
(MessageKey.FORCE_LOGIN_PLAYER_OFFLINE or a suitable denial message). Ensure you
reference the same permission constant/method as used for the online-player path
and keep the lowercasing key consistent.

In
`@authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java`:
- Around line 123-132: The branch handling !encVerifyTokenOpt.isPresent()
resumes login without enabling channel encryption or decrypting encSharedSecret,
causing a connection desync after ENCRYPTION_RESPONSE; fix by replicating the
encryption setup used when a plain verify token is present: decrypt
encSharedSecret, install the AES encryption/decryption handlers on the channel
(same logic used elsewhere to enable encryption), and only then call
loginVerifier.cleanupPending(connectionKey), event.setCancelled(true) and
resumeLogin(user, username, clientVersion, playerUUID); alternatively, if
decryption cannot succeed here, abort the login instead of resuming to avoid
desync (use the same abort path as the verified-token failure handling).
- Around line 88-105: The async task started in
PremiumVerificationPacketListener must handle exceptions so cancelled
LOGIN_START doesn't leave connections hanging; wrap the
CompletableFuture.runAsync body in a try/catch, and on any thrown exception call
the same recovery path used in handleEncryptionResponse() — either
resumeLogin(user, username, clientVersion, playerUUID) or explicitly
fail/disconnect the user (using the existing disconnect/resume helpers) and log
the error; reference the pendingPremiumCache check, dataSource.getAuth(...),
loginVerifier.startVerification(...), and resumeLogin(...) when implementing the
catch to ensure every error path resumes or terminates the login flow.

In
`@authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java`:
- Around line 164-175: In BungeeReceiver, before calling
premiumService.finalizePendingPremium(player, playerId) (after
pendingPremiumCache.removePending(name)), verify that the player UUID returned
by player.getUniqueId() equals the pendingUuid removed from the cache; if they
match call finalizePendingPremium, otherwise log a warning similar to the
existing message and call bungeeSender.sendPremiumUnset(name) to discard the
pending state — i.e., replace the unconditional finalizePendingPremium call with
an equality check using playerId.equals(pendingUuid) and the same fallback
handling used for non-v4 UUIDs.
- Around line 109-118: The captured Player reference (carrier) used in the async
DB query may be stale if the player disconnects; modify the sync callback
scheduled from bukkitService.scheduleSyncTaskFromOptionallyAsyncTask so it
verifies the player is still online before calling bungeeSender.sendPremiumList
and logger.info (e.g., check carrier.isOnline() or re-resolve the player via its
UUID), and bail out if the player is no longer connected to avoid sending plugin
messages through a disconnected Player.

In
`@authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java`:
- Around line 144-150: sendPremiumList currently writes the entire
premiumUsernames as a single UTF string which can exceed BungeeCord's
32,767-byte plugin message limit; modify sendPremiumList to chunk the list and
send multiple plugin messages from the carrier. Build each chunk by iterating
premiumUsernames and accumulating comma-separated names until adding the next
name would exceed a safe byte-size threshold (e.g. 32KB minus overhead when
serialized as UTF), then for each chunk create a ByteArrayDataOutput
(ByteStreams.newDataOutput()), write MessageType.PREMIUM_LIST.getId() plus
simple chunk metadata (partIndex and totalParts or a continuation flag) and the
chunk payload, and send each chunk via the existing send logic; ensure
ByteArrayDataOutput/UTF sizing is respected so no single message exceeds the
proxy limit.

In `@authme-core/src/main/java/fr/xephi/authme/service/PendingPremiumCache.java`:
- Around line 62-72: The getPendingUuid method has a TOCTOU where
pending.remove(name.toLowerCase(Locale.ROOT)) can delete a newly added entry;
change the unconditional remove to the conditional remove(key, value) overload
so you only remove if the mapped value is the same expired PendingEntry you
observed. In practice, capture the key and the retrieved PendingEntry (from
pending.get(...)), check expiry as now done, and call pending.remove(key, entry)
instead of pending.remove(key) so concurrent addPending calls won't clobber a
fresh valid record; this relies on PendingEntry's record equals to match both
mojangUuid and expiresAt.

In `@authme-core/src/main/java/fr/xephi/authme/service/PreJoinDialogService.java`:
- Around line 63-116: approvePreJoinForceLogin can race with
clear/unregisterPreJoinFuture because it reads pendingPreJoinByName and
pendingPreJoinFutures then adds to pendingForceLogins without atomicity; make
the check-and-set operations atomic by introducing per-player synchronization
(e.g. a lock object stored in a ConcurrentHashMap keyed by UUID or by
synchronizing on a dedicated PlayerEntry object that consolidates
pendingPreJoinByName/pendingPreJoinFutures/pendingForceLogins state). Update
approvePreJoinForceLogin to acquire the per-player lock (or synchronize on the
PlayerEntry), re-check that uuid and future are present, add to
pendingForceLogins and only then complete the future, and release the lock;
modify clear/unregisterPreJoinFuture to acquire the same lock when removing
state so they cannot interleave and leave stale flags. Ensure methods
referenced: approvePreJoinForceLogin, unregisterPreJoinFuture, clear and the
collections pendingPreJoinByName, pendingPreJoinFutures, pendingForceLogins use
the same synchronization.

In `@authme-core/src/main/resources/messages/messages_de.yml`:
- Line 1: The German locale file contains mojibake (e.g., strings like
"bestätigen", "für", "Überprüfung") due to wrong encoding; reopen the
messages_de.yml, convert and save it as UTF-8 (without a mismatched BOM),
restore the original German umlauts and special characters in all message values
(registration, login, recovery, 2FA, premium messages) and re-run a quick grep
for the example corrupted tokens to confirm no stray mojibake remains.

---

Outside diff comments:
In `@authme-core/src/main/resources/messages/messages_br.yml`:
- Around line 1-241: This file contains double-encoded UTF-8 (mojibake) in many
translation values; revert the edited translations back to their original pre-PR
Portuguese strings for keys like registration.register_request,
password.match_error, login.login_request, error.unregistered_user,
on_join_validation.invalid_name_case, recovery.code.code_sent, dialog.* and
other top-level sections, then re-add only the premium block (premium.* keys)
using a UTF-8-aware editor and save the file as UTF-8 (no BOM) so accented
characters (e.g., Tradução, usuários, Você, não, reconecte-se) are stored
correctly; ensure no other keys are changed beyond restoring correct Portuguese
text.

In `@authme-core/src/main/resources/messages/messages_et.yml`:
- Around line 1-238: Many Estonian strings were double-encoded (mojibake) in
keys like registration.register_request, password.match_error,
dialog.button.cancel, time.day and others; revert all edits to the pre-existing
keys (everything except the premium block) so their original UTF-8 text is
restored, then re-add only the premium block (premium.feature_disabled ...
premium.pending_fail) ensuring the file is saved in UTF-8; open/save with a
UTF-8-aware editor or run a transliteration fix on the corrupted values (e.g.
for a string s: s.encode('latin-1').decode('utf-8')) to convert garbled entries
back to proper UTF-8 before committing.

---

Duplicate comments:
In `@authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java`:
- Around line 89-103: The pending-entry TTL is only enforced when starting
verifications, so reads can return stale entries; update all read/remove methods
(e.g., hasPending, getPendingUsername, getPendingPlayerUUID and the methods
around lines 133-138 such as completeVerification/removePending) to check expiry
before returning data. Implement this by either invoking
evictStalePendingEntries() at the start of each read/remove method or by
checking the PendingVerification's timestamp against PENDING_TTL_MS (and
removing the map entry if expired) so expired handshakes are treated as absent
and not returned or accepted.

In `@authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java`:
- Around line 158-160: Normalize the admin target once at the start of the async
task and use that normalized key for all persistence/cache calls: replace uses
of the raw playerName inside the bukkitService.runTaskOptionallyAsync lambda
(where dataSource.getAuth(playerName) and
playerCache.isAuthenticated(playerName) are called) with a single normalized
variable (e.g., normalizedPlayer or normalizedName) and use that same variable
for any subsequent lookups/updates; apply the same change to the other block
referenced (the logic around lines 228-249) so all dataSource and playerCache
interactions consistently use the normalized key.

In `@docs/premium.md`:
- Around line 54-79: Add a language tag to the fenced code block that begins
with "Client                         Server (AuthMe + PacketEvents)       
Mojang" so the markdown linter (MD040) is satisfied; change the opening fence
from ``` to ```text (or another appropriate language such as ```nohighlight) and
leave the block contents unchanged.

---

Nitpick comments:
In `@authme-core/src/main/resources/plugin.yml`:
- Around line 326-338: The permission node authme.player.premium currently has
default: true (as does authme.player.freemium), which means any player can run
/premium to attempt premium enrollment; update the authme.player.premium
description (or README/wiki) to explicitly state that default:true allows all
players to self-enroll and that server operators must explicitly deny this
permission to prevent self-enrollment, and mention that enrollment is still
gated by Mojang session verification for cryptographic protection.

In
`@authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java`:
- Around line 97-111: The test setup stubs PremiumSettings.ENABLE_PREMIUM to
false so AsynchronousJoin.canBypassWithPremium() is never exercised; add new
unit tests in AsynchronousJoinTest that set
given(service.getProperty(PremiumSettings.ENABLE_PREMI)).willReturn(true) and
exercise AsynchronousJoin.canBypassWithPremium() for (1) a v4 UUID path that
should bypass immediately (proxy-verified), (2) a v3 UUID path that invokes the
PremiumLoginVerifier mock (verify interactions with PremiumLoginVerifier) and
(3) pending verification flows where the verifier returns success and failure to
assert both acceptance and denial handling; update/instantiate the
PremiumLoginVerifier mock behavior in each test and reuse existing helper
methods from setUp() to schedule tasks as needed.

In
`@authme-core/src/test/java/fr/xephi/authme/service/bungeecord/BungeeReceiverTest.java`:
- Around line 99-127: Add tests that exercise the premium verification branch by
stubbing a pending premium UUID and the player's UniqueId: create three new test
cases (modeled on
shouldQueueSessionAndForceLoginWhenPerformLoginReceivedForOnlinePlayer) where
you set pendingPremiumCache to return a UUID for playerName, then set the mocked
Player.getUniqueId() to (a) a matching v4 UUID, (b) a v3/offline-mode UUID, and
(c) a non-matching UUID; in each test call receiver.onPluginMessageReceived(...)
and verify the expected interactions with
proxySessionManager.processProxySessionMessage, management.forceLoginFromProxy
and bungeeSender.sendAuthMeBungeecordMessage and also verify pendingPremiumCache
removal or premiumService invocation as appropriate to each branch so the
premium verification logic in BungeeReceiver is covered.

In `@docs/premium.md`:
- Around line 183-187: Clarify that premium bypass is checked by Mojang UUID
while AuthMe stores accounts by lowercase player name, so after a username
change the premium check will still recognize the player (UUID), but AuthMe will
not automatically map the new name to the old name-keyed account; instruct
readers to either run /authme premium to re-link the new name to their premium
status or use their server's AuthMe account-migration/rename feature (or a
plugin setting that enables UUID-based account linking) to migrate the old name
record to the new lowercase name so the player does not need to fully
re-register.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c94c52d-d16e-411c-b630-0c3f91eb22a5

📥 Commits

Reviewing files that changed from the base of the PR and between ab16b22 and 2cc2adb.

📒 Files selected for processing (107)
  • README.md
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/AuthMeBungeePlugin.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyBridge.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyConfiguration.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/config/BungeeConfigProperties.java
  • authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeProxyBridgeTest.java
  • authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeReloadCommandTest.java
  • authme-core/src/main/java/fr/xephi/authme/command/CommandInitializer.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetFreemiumAdminCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetPremiumAdminCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/premium/FreemiumCommand.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/premium/PremiumCommand.java
  • authme-core/src/main/java/fr/xephi/authme/data/auth/PlayerAuth.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/AbstractSqlDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/Columns.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/DataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/MySQL.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/SQLite.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/AesCfb8Decoder.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/AesCfb8Encoder.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsListenerRegistry.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsService.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java
  • authme-core/src/main/java/fr/xephi/authme/message/MessageKey.java
  • authme-core/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java
  • authme-core/src/main/java/fr/xephi/authme/permission/AdminPermission.java
  • authme-core/src/main/java/fr/xephi/authme/permission/PlayerPermission.java
  • authme-core/src/main/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapter.java
  • authme-core/src/main/java/fr/xephi/authme/platform/PacketInterceptionAdapter.java
  • authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
  • authme-core/src/main/java/fr/xephi/authme/service/MojangApiService.java
  • authme-core/src/main/java/fr/xephi/authme/service/PendingPremiumCache.java
  • authme-core/src/main/java/fr/xephi/authme/service/PreJoinDialogService.java
  • authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java
  • authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/PremiumSettings.java
  • authme-core/src/main/resources/messages/messages_bg.yml
  • authme-core/src/main/resources/messages/messages_br.yml
  • authme-core/src/main/resources/messages/messages_cz.yml
  • authme-core/src/main/resources/messages/messages_de.yml
  • authme-core/src/main/resources/messages/messages_en.yml
  • authme-core/src/main/resources/messages/messages_eo.yml
  • authme-core/src/main/resources/messages/messages_es.yml
  • authme-core/src/main/resources/messages/messages_et.yml
  • authme-core/src/main/resources/messages/messages_eu.yml
  • authme-core/src/main/resources/messages/messages_fi.yml
  • authme-core/src/main/resources/messages/messages_fr.yml
  • authme-core/src/main/resources/messages/messages_gl.yml
  • authme-core/src/main/resources/messages/messages_hu.yml
  • authme-core/src/main/resources/messages/messages_id.yml
  • authme-core/src/main/resources/messages/messages_it.yml
  • authme-core/src/main/resources/messages/messages_ja.yml
  • authme-core/src/main/resources/messages/messages_ko.yml
  • authme-core/src/main/resources/messages/messages_lt.yml
  • authme-core/src/main/resources/messages/messages_nl.yml
  • authme-core/src/main/resources/messages/messages_pl.yml
  • authme-core/src/main/resources/messages/messages_pt.yml
  • authme-core/src/main/resources/messages/messages_ro.yml
  • authme-core/src/main/resources/messages/messages_ru.yml
  • authme-core/src/main/resources/messages/messages_si.yml
  • authme-core/src/main/resources/messages/messages_sk.yml
  • authme-core/src/main/resources/messages/messages_sr.yml
  • authme-core/src/main/resources/messages/messages_tr.yml
  • authme-core/src/main/resources/messages/messages_uk.yml
  • authme-core/src/main/resources/messages/messages_vn.yml
  • authme-core/src/main/resources/messages/messages_zhcn.yml
  • authme-core/src/main/resources/messages/messages_zhhk.yml
  • authme-core/src/main/resources/messages/messages_zhmc.yml
  • authme-core/src/main/resources/messages/messages_zhtw.yml
  • authme-core/src/main/resources/plugin.yml
  • authme-core/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java
  • authme-core/src/test/java/fr/xephi/authme/command/executable/authme/ForceLoginCommandTest.java
  • authme-core/src/test/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapterTest.java
  • authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java
  • authme-core/src/test/java/fr/xephi/authme/service/PreJoinDialogServiceTest.java
  • authme-core/src/test/java/fr/xephi/authme/service/bungeecord/BungeeReceiverTest.java
  • authme-core/src/test/resources/fr/xephi/authme/datasource/sql-initialize.sql
  • authme-core/src/test/resources/plugin.yml
  • authme-folia/src/main/resources/plugin.yml
  • authme-paper-common/pom.xml
  • authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java
  • authme-paper-common/src/main/java/fr/xephi/authme/platform/AbstractPaperPlatformAdapter.java
  • authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java
  • authme-paper/src/main/resources/plugin.yml
  • authme-spigot-1.21/src/main/resources/plugin.yml
  • authme-spigot-legacy/src/main/resources/plugin.yml
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/AuthMeVelocityPlugin.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyBridge.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyConfiguration.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/config/VelocityConfigProperties.java
  • authme-velocity/src/test/java/fr/xephi/authme/velocity/VelocityProxyBridgeTest.java
  • docs/commands.md
  • docs/config.md
  • docs/permission_nodes.md
  • docs/premium.md
  • docs/proxies/bungee/config.yml
  • docs/proxies/velocity/config.yml
  • docs/translations.md
✅ Files skipped from review due to trivial changes (30)
  • docs/proxies/velocity/config.yml
  • authme-core/src/main/java/fr/xephi/authme/datasource/Columns.java
  • docs/config.md
  • authme-core/src/main/java/fr/xephi/authme/data/auth/PlayerAuth.java
  • authme-core/src/main/resources/messages/messages_gl.yml
  • authme-core/src/main/java/fr/xephi/authme/datasource/MySQL.java
  • authme-core/src/main/resources/messages/messages_ru.yml
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/AuthMeBungeePlugin.java
  • authme-core/src/main/resources/messages/messages_ja.yml
  • authme-core/src/main/resources/messages/messages_zhhk.yml
  • authme-core/src/main/resources/messages/messages_bg.yml
  • authme-core/src/main/resources/messages/messages_zhcn.yml
  • authme-core/src/main/resources/messages/messages_es.yml
  • authme-core/src/main/resources/messages/messages_zhmc.yml
  • authme-core/src/main/java/fr/xephi/authme/command/executable/premium/PremiumCommand.java
  • docs/translations.md
  • authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeReloadCommandTest.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java
  • authme-core/src/main/java/fr/xephi/authme/service/MojangApiService.java
  • authme-folia/src/main/resources/plugin.yml
  • authme-core/src/main/java/fr/xephi/authme/permission/AdminPermission.java
  • authme-core/src/main/resources/messages/messages_tr.yml
  • authme-core/src/main/resources/messages/messages_it.yml
  • authme-core/src/main/resources/messages/messages_nl.yml
  • authme-core/src/main/resources/messages/messages_ko.yml
  • docs/commands.md
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/config/BungeeConfigProperties.java
  • authme-core/src/main/resources/messages/messages_fi.yml
  • authme-core/src/main/resources/messages/messages_cz.yml
  • authme-core/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
🚧 Files skipped from review as they are similar to previous changes (44)
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/config/VelocityConfigProperties.java
  • authme-core/src/main/resources/messages/messages_eu.yml
  • authme-core/src/main/resources/messages/messages_sk.yml
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/PremiumSettings.java
  • authme-core/src/test/resources/plugin.yml
  • authme-paper-common/pom.xml
  • docs/proxies/bungee/config.yml
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
  • authme-core/src/test/java/fr/xephi/authme/platform/AbstractSpigotPlatformAdapterTest.java
  • authme-core/src/main/resources/messages/messages_id.yml
  • authme-core/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java
  • authme-core/src/main/resources/messages/messages_fr.yml
  • authme-core/src/main/java/fr/xephi/authme/command/executable/authme/SetFreemiumAdminCommand.java
  • authme-velocity/src/test/java/fr/xephi/authme/velocity/VelocityProxyBridgeTest.java
  • authme-core/src/main/resources/messages/messages_vn.yml
  • authme-core/src/main/java/fr/xephi/authme/command/CommandInitializer.java
  • authme-core/src/main/resources/messages/messages_pt.yml
  • authme-core/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java
  • authme-core/src/main/java/fr/xephi/authme/datasource/SQLite.java
  • authme-core/src/main/resources/messages/messages_hu.yml
  • authme-core/src/main/resources/messages/messages_en.yml
  • authme-core/src/main/resources/messages/messages_zhtw.yml
  • authme-core/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsListenerRegistry.java
  • authme-spigot-legacy/src/main/resources/plugin.yml
  • authme-core/src/main/resources/messages/messages_pl.yml
  • authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java
  • authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeProxyBridgeTest.java
  • README.md
  • authme-core/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java
  • authme-core/src/main/java/fr/xephi/authme/command/executable/premium/FreemiumCommand.java
  • authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyConfiguration.java
  • authme-core/src/main/resources/messages/messages_lt.yml
  • authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java
  • authme-core/src/main/resources/messages/messages_uk.yml
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PacketEventsService.java
  • authme-core/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyBridge.java
  • authme-paper/src/main/resources/plugin.yml
  • authme-core/src/main/java/fr/xephi/authme/listener/packetevents/AesCfb8Encoder.java
  • authme-paper-common/src/main/java/fr/xephi/authme/platform/AbstractPaperPlatformAdapter.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/AuthMeVelocityPlugin.java
  • authme-velocity/src/main/java/fr/xephi/authme/velocity/VelocityProxyConfiguration.java
  • authme-spigot-1.21/src/main/resources/plugin.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build_and_test (21)
  • GitHub Check: build_and_test (21)
🧰 Additional context used
🪛 ast-grep (0.42.1)
authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java

[warning] 193-193: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)


[warning] 187-187: Cipher in ECB mode is detected. ECB mode produces the same output for the same input each time which allows an attacker to intercept and replay the data. Further, ECB mode does not provide any integrity checking. See https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY.
Context: Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
Note: [CWE-327] Use of a Broken or Risky Cryptographic Algorithm. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(ecb-cipher-java)

🪛 LanguageTool
docs/permission_nodes.md

[style] ~31-~31: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...mand to set the first AuthMe spawn. - authme.admin.setfreemium – Administrator com...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~32-~32: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... disable premium mode for a player. - authme.admin.setpremium – Administrator comm...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~67-~67: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ssion to see the own email address. - authme.player.freemium – Permission to disab...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~68-~68: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...Permission to disable premium mode. - authme.player.login – Command permission to ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~69-~69: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...in** – Command permission to login. - authme.player.logout – Command permission to...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~69-~69: Ensure spelling is correct
Context: ...player.logout** – Command permission to logout. - authme.player.premium – Permissi...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~70-~70: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...t** – Command permission to logout. - authme.player.premium – Permission to enable...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.22.1)
docs/premium.md

[warning] 54-54: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (20)
authme-core/src/main/resources/messages/messages_si.yml (1)

217-238: LGTM — all premium message keys accounted for with correct placeholders.

All 18 PREMIUM_* / PREMIUM_ADMIN_* constants declared in MessageKey.java (lines 456–507) map exactly to the keys introduced here. Every admin-scoped key that carries "%name" in its MessageKey declaration includes the placeholder, and premium.admin.kick_reason (no declared placeholder) correctly omits it. The pending_kick / pending_fail keys are correctly nested directly under premium: rather than under premium.admin:, matching their paths in the enum.

authme-core/src/main/java/fr/xephi/authme/permission/PlayerPermission.java (1)

86-96: LGTM!

Enum syntax is correct — the ; terminator was properly moved from DISABLE_TWO_FACTOR_AUTH to the new last constant USE_FREEMIUM. Node strings are consistent with their declarations in plugin.yml and docs/permission_nodes.md.

authme-core/src/main/resources/plugin.yml (3)

42-42: LGTM!

The /authme usage string correctly appends premium|freemium.


82-87: LGTM!

/premium and /freemium are correctly registered as top-level commands, consistent with the plugin's existing command registration pattern.


112-113: LGTM!

authme.admin.setfreemium and authme.admin.setpremium are correctly inserted alphabetically in the authme.admin.* children block and their default: op definitions are consistent with existing admin permission nodes and the test fixture.

Also applies to: 181-186

docs/permission_nodes.md (1)

2-2: LGTM!

New entries (authme.admin.setfreemium, authme.admin.setpremium, authme.player.freemium, authme.player.premium) are correctly inserted in alphabetical order and their descriptions match plugin.yml verbatim. The LanguageTool style/grammar hints are false positives for this auto-generated list format.

Also applies to: 31-32, 67-83

authme-core/src/test/java/fr/xephi/authme/service/PreJoinDialogServiceTest.java (1)

17-85: LGTM!

All six test methods cover the key behavioral contracts of PreJoinDialogService: single-consume semantics, future completion on approval, correct false returns for missing/unregistered state, and full-state clearing. The test assertions are correct, including the future.getNow("sentinel") check at Line 39 (since isDone() is already asserted true, getNow returns the actual completed value null rather than the sentinel).

authme-core/src/main/resources/messages/messages_ro.yml (1)

217-237: LGTM — past review issue resolved, premium section complete.

All 18 premium.* and premium.admin.* keys are present and correctly structured under the new top-level premium block. The previously reported nesting bug (admin.spawn.first_not_defined accidentally placed under premium.admin) no longer exists: Line 215 correctly places first_not_defined under admin.spawn.

authme-core/src/main/java/fr/xephi/authme/message/MessageKey.java (1)

455-507: LGTM — all 18 new premium constants are correct.

Key/tag mappings align exactly with their callsites in PremiumService (verified against all four usage snippets): keys with %name interpolation declare the "%name" tag, keys used only with retrieveSingle or send with no replacements declare no tags, and all YAML key paths match the new premium.* blocks added across the locale files.

authme-core/src/main/resources/messages/messages_sr.yml (1)

218-238: LGTM — Serbian premium section is complete and correctly encoded.

All 18 premium.* and premium.admin.* keys are present with correct Cyrillic text (no encoding corruption). The 2-space indent is inconsistent with the 4-space style used in the rest of this file, but YAML parsers (including snakeyaml) handle mixed indent levels without issues.

authme-core/src/main/resources/messages/messages_eo.yml (1)

218-238: LGTM — Esperanto premium section is complete and correctly encoded.

All 18 premium keys are present, Esperanto-specific diacritics (ĝ, ĉ, ŝ, ĵ, ĉ) are correctly encoded, and indentation is consistent with the rest of the file.

authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java (1)

37-60: LGTM!

The new dependency injections for DataSource, PendingPremiumCache, and PremiumService are properly added and stored, enabling the premium verification flow.

authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java (6)

118-126: LGTM!

The new dependency injections for premium verification (PremiumLoginVerifier, PendingPremiumCache, PremiumService) are properly declared and will be used in the canBypassWithPremium flow.


189-210: LGTM!

The use of forceLoginFromProxy() for proxy-session resumption (line 197) and premium bypass with proxy enabled (line 205) is appropriate. The quiet mode prevents "already logged in" errors when BungeeReceiver.performLogin() has already completed the login concurrently.


227-232: LGTM!

This guard prevents the race condition where BungeeReceiver.performLogin() could authenticate the player before the async join task reaches limbo creation. Without this, the player would be frozen in limbo permanently.


259-263: LGTM!

A second authentication guard inside the sync task provides defense-in-depth against the timing window between scheduling and execution.


284-287: LGTM!

The pendingForceLogin path correctly triggers an async forceLogin() call after limbo creation, enabling pre-join dialog approval to complete the login flow.


350-398: LGTM!

The canBypassWithPremium method implements a sound security model:

  • UUID v4 (online-mode) is trusted when the backend is proxy-firewalled
  • UUID v3 (offline-mode) requires cryptographic verification via PremiumLoginVerifier
  • Pending premium verification correctly compares the connecting player's UUID against the stored pending UUID before finalizing

The bungeeSender.sendPremiumUnset call at line 383 is safe even without a proxy, as BungeeSender guards against disabled state internally.

authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java (2)

197-214: LGTM!

This test correctly verifies that proxy session resumption uses forceLoginFromProxy() (quiet mode) and skips limbo creation, preventing conflicts with concurrent BungeeReceiver logins.


236-253: LGTM!

This test correctly verifies the pendingForceLogin path: limbo creation followed by forceLogin(), without triggering password-based login or dialogs.

Comment on lines 44 to +52
if (player == null || !player.isOnline()) {
messages.send(sender, MessageKey.FORCE_LOGIN_PLAYER_OFFLINE);
// Player may be blocked in the pre-join dialog (Paper/Folia configuration phase).
// Approving the force-login completes the blocking future so the player proceeds to
// PLAY state, where AsynchronousJoin will call forceLogin() on their behalf.
if (preJoinDialogService.approvePreJoinForceLogin(playerName.toLowerCase(Locale.ROOT))) {
messages.send(sender, MessageKey.FORCE_LOGIN_SUCCESS, playerName);
} else {
messages.send(sender, MessageKey.FORCE_LOGIN_PLAYER_OFFLINE);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't bypass CAN_LOGIN_BE_FORCED in the pre-join path.

This branch force-approves pre-join players by name before the target's CAN_LOGIN_BE_FORCED protection is checked, so accounts that deny forced login lose that protection during the configuration phase. Please add an equivalent permission check for pending pre-join connections, or fail closed here until one exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java`
around lines 44 - 52, The pre-join branch calls
preJoinDialogService.approvePreJoinForceLogin(...) without checking the target's
CAN_LOGIN_BE_FORCED permission, so add a permission check before approving:
query the permission/authorization routine used elsewhere (e.g.,
PermissionNode.CAN_LOGIN_BE_FORCED or the same permission-check helper used for
online players) for playerName.toLowerCase(Locale.ROOT) and only call
preJoinDialogService.approvePreJoinForceLogin(...) if that check allows forced
logins; otherwise treat as denied and send the existing failure message
(MessageKey.FORCE_LOGIN_PLAYER_OFFLINE or a suitable denial message). Ensure you
reference the same permission constant/method as used for the online-player path
and keep the lowercasing key consistent.

Comment on lines +88 to +105
// DB lookup must happen off the Netty event loop to avoid blocking
CompletableFuture.runAsync(() -> {
String lowerName = username.toLowerCase(Locale.ROOT);
fr.xephi.authme.data.auth.PlayerAuth auth = dataSource.getAuth(lowerName);

boolean isPremium = auth != null && auth.isPremium();
boolean isPending = !isPremium && pendingPremiumCache.isPending(username);

if (isPremium || isPending) {
byte[] verifyToken = loginVerifier.startVerification(connectionKey, username, playerUUID);
WrapperLoginServerEncryptionRequest encReq = new WrapperLoginServerEncryptionRequest(
"", loginVerifier.getPublicKey(), verifyToken, true);
user.sendPacket(encReq);
} else {
// Not premium (and not pending) — resume normal login without verification
resumeLogin(user, username, clientVersion, playerUUID);
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java

Repository: TuxCoding/AuthMeReloaded

Length of output: 12583


Always resolve the cancelled login on async failures.

LOGIN_START is cancelled before this task runs, but exceptions inside runAsync(...) are never observed. A DB/cache failure here leaves the connection stuck because neither resumeLogin(...) nor a disconnect path runs. Wrap the task body and explicitly resume or fail the login on error (follow the pattern used in handleEncryptionResponse() at lines 158–168).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java`
around lines 88 - 105, The async task started in
PremiumVerificationPacketListener must handle exceptions so cancelled
LOGIN_START doesn't leave connections hanging; wrap the
CompletableFuture.runAsync body in a try/catch, and on any thrown exception call
the same recovery path used in handleEncryptionResponse() — either
resumeLogin(user, username, clientVersion, playerUUID) or explicitly
fail/disconnect the user (using the existing disconnect/resume helpers) and log
the error; reference the pendingPremiumCache check, dataSource.getAuth(...),
loginVerifier.startVerification(...), and resumeLogin(...) when implementing the
catch to ensure every error path resumes or terminates the login flow.

Comment on lines +164 to +175
UUID pendingUuid = pendingPremiumCache.removePending(name);
if (pendingUuid != null) {
UUID playerId = player.getUniqueId();
if (playerId.version() == 4) {
premiumService.finalizePendingPremium(player, playerId);
} else {
// Unexpected: proxy sent PERFORM_LOGIN but player has an offline UUID.
// Discard the pending state and let the player use a password.
logger.warning("Received PERFORM_LOGIN for pending-premium player " + name
+ " but their UUID is not v4 — discarding pending verification");
bungeeSender.sendPremiumUnset(name);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing UUID comparison before finalizing premium verification.

At line 168, finalizePendingPremium is called with playerId without verifying it matches pendingUuid. This differs from AsynchronousJoin.canBypassWithPremium() (line 370) which correctly compares playerId.equals(pendingUuid) before proceeding.

Without this check, a player could theoretically have a pending premium entry for one Mojang UUID but connect with a different Mojang account, and the wrong UUID would be stored.

Proposed fix
             UUID pendingUuid = pendingPremiumCache.removePending(name);
             if (pendingUuid != null) {
                 UUID playerId = player.getUniqueId();
-                if (playerId.version() == 4) {
+                if (playerId.version() == 4 && playerId.equals(pendingUuid)) {
                     premiumService.finalizePendingPremium(player, playerId);
-                } else {
+                } else if (playerId.version() != 4) {
                     // Unexpected: proxy sent PERFORM_LOGIN but player has an offline UUID.
                     // Discard the pending state and let the player use a password.
                     logger.warning("Received PERFORM_LOGIN for pending-premium player " + name
                         + " but their UUID is not v4 — discarding pending verification");
                     bungeeSender.sendPremiumUnset(name);
+                } else {
+                    // UUID mismatch: player connected with a different Mojang account
+                    logger.warning("Received PERFORM_LOGIN for pending-premium player " + name
+                        + " but their UUID does not match the pending UUID — discarding pending verification");
+                    bungeeSender.sendPremiumUnset(name);
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UUID pendingUuid = pendingPremiumCache.removePending(name);
if (pendingUuid != null) {
UUID playerId = player.getUniqueId();
if (playerId.version() == 4) {
premiumService.finalizePendingPremium(player, playerId);
} else {
// Unexpected: proxy sent PERFORM_LOGIN but player has an offline UUID.
// Discard the pending state and let the player use a password.
logger.warning("Received PERFORM_LOGIN for pending-premium player " + name
+ " but their UUID is not v4 — discarding pending verification");
bungeeSender.sendPremiumUnset(name);
}
UUID pendingUuid = pendingPremiumCache.removePending(name);
if (pendingUuid != null) {
UUID playerId = player.getUniqueId();
if (playerId.version() == 4 && playerId.equals(pendingUuid)) {
premiumService.finalizePendingPremium(player, playerId);
} else if (playerId.version() != 4) {
// Unexpected: proxy sent PERFORM_LOGIN but player has an offline UUID.
// Discard the pending state and let the player use a password.
logger.warning("Received PERFORM_LOGIN for pending-premium player " + name
" but their UUID is not v4 — discarding pending verification");
bungeeSender.sendPremiumUnset(name);
} else {
// UUID mismatch: player connected with a different Mojang account
logger.warning("Received PERFORM_LOGIN for pending-premium player " + name
" but their UUID does not match the pending UUID — discarding pending verification");
bungeeSender.sendPremiumUnset(name);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@authme-core/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java`
around lines 164 - 175, In BungeeReceiver, before calling
premiumService.finalizePendingPremium(player, playerId) (after
pendingPremiumCache.removePending(name)), verify that the player UUID returned
by player.getUniqueId() equals the pendingUuid removed from the cache; if they
match call finalizePendingPremium, otherwise log a warning similar to the
existing message and call bungeeSender.sendPremiumUnset(name) to discard the
pending state — i.e., replace the unconditional finalizePendingPremium call with
an equality check using playerId.equals(pendingUuid) and the same fallback
handling used for non-v4 UUIDs.

Comment thread authme-core/src/main/resources/messages/messages_de.yml
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants